from xmlrpclib import ServerProxy, Fault
from optparse import OptionParser

from config import ServerConfiguration
from config import create_download_request

class ClientProxy:
    # Network Model.
    def __init__(self, address, port = None):
        self.address = address
        self.port = port

    def getProxyUrl(self):
        portstr = '' if self.port is None else ':%d' % int(self.port)
        return 'http://%s%s' % (self.address, portstr)

    def getProxy(self):
        return ServerProxy(self.getProxyUrl())

    def invoke(self, action, *args):
        rpc = getattr(self.getProxy(), action)
        # debug()
        return rpc(*args)

class DownloadControllerClient(ClientProxy):
    # Validate Specific Actions -- Interpret Success/Result Results.
    def initiate(self, url, name, **values):
        req = create_download_request(url, name, **values)
        return self.invoke('download.initiate', req)
    def cancel(self, name, complete = None, delete = None):
        return self.invoke('download.cancel', name, complete, delete)
    def pause(self, name):
        return self.invoke('download.pause', name)
    def resume(self, name):
        return self.invoke('download.resume', name)

    def complete(self, name, delete = None):
        return self.invoke('download.complete', name)
    def status(self, name):
        return self.invoke('download.status', name)

    def query(self, criteria):
        return self.invoke('download.query', criteria)
    def restart(self, name):
        return self.invoke('download.restart', name)

    def server_status(self):
        return self.invoke('server.status')
    def server_start(self):
        return self.invoke('server.start')
    def server_stop(self):
        return self.invoke('server.stop')
    def server_shutdown(self):
        return self.invoke('server.shutdown')

# Utilizes Controller Client Internally.
DEFAULT_CLIENT_PROGRAM_NAME = 'downmgr-client'

class CommandClient:
    # Configure and invoke commands.
    class UsageError(Exception):
        def __init__(self, message = None, client = None):
            self._message = message
            self.client = client

        def getUsage(self):
            if self._message:
                return self._message

            return self.client.getUsage()

    def usageError(self, message):
        raise self.UsageError(message, client = self)

    def __init__(self, config, controller, prog = None):
        self.prog = prog or DEFAULT_CLIENT_PROGRAM_NAME
        self.controller = controller

    def getParser(self):
        try: return self.parser
        except AttributeError:
            parser = OptionParser(prog = self.prog)
            self.setupCmdlnParser(parser)

            self.parser = parser
            return parser

    def parseCmdln(self, args):
        argv = list(args)
        return self.getParser().parse_args(argv)

    @classmethod
    def getUsage(self):
        # Todo: integrate command-parser.
        return (self.__doc__)

    def setupCmdlnParser(self, parser):
        parser.add_option('-C', '--config-file')
        parser.add_option('-p', '--port')

        parser.add_option('--remote-address')

        for (opt_args, opt_kwd) in self.PARSER_OPTIONS:
            parser.add_option(*opt_args, **opt_kwd)

    def doCommand(self, args):
        (options, args) = self.parseCmdln(args)
        return self.doCommandWithArgs(options, args)

    # Override these:
    PARSER_OPTIONS = []

    def doCommandWithArgs(self, options, args):
        pass

def Option(*args, **kwd):
    return (args, kwd)

class SimpleDownloadActionClient(CommandClient):
    # Simple Download Control.
    PARSER_OPTIONS = [Option('--name'             ),
                      Option('--idnum', type = int)]

    def getName(self, options, args):
        name = options.idnum
        if name is None:
            name = options.name
            if name is None:
                try: name = args[0]
                except IndexError:
                    self.usageError('Expected download <name-or-id>!')

        return name

    def doCommandWithArgs(self, options, args):
        name = self.getName(options, args)
        action = getattr(self.controller, self.ACTION)
        action(name)

class SimpleServerActionClient(CommandClient):
    def doCommandWithArgs(self, options, args):
        action = getattr(self.controller, self.ACTION)
        self.displayResponse(action())

    def displayResponse(self, response):
        if response is not None:
            print '%s Response: %r' % (self.__class__.__name__, response)

# Command Implementations.
# Todo: Document.
class Initiate(CommandClient):
    'Start a download by building a description.'

    PARSER_OPTIONS = [Option('--name'    ),
                      Option('--url'     ),
                      Option('--group'   ),
                      Option('--filename'),
                      Option('--folder'  ),
                      Option('--order'   )]

    def doCommandWithArgs(self, options, args):
        try: url = options.url or args[0]
        except IndexError:
            return self.usageError('Expected <url> argument!')

        self.controller.initiate(url, options.name,
                                 order = options.order,
                                 group = options.group,
                                 filename = options.filename,
                                 folder = options.folder)

class Query(CommandClient):
    QUERYABLE = ['name', 'url', 'group', 'filename', 'folder', 'order', 'state']
    PARSER_OPTIONS = [Option('--%s' % q) for q in QUERYABLE]

    def doCommandWithArgs(self, options, args):
        criteria = [(k, getattr(options, k)) for k in self.QUERYABLE]
        criteria = dict((k, v) for (k, v) in criteria if v is not None)
        self.displayResponse(self.controller.query(criteria))

    def displayResponse(self, downloads):
        print '%s Matches:' % self.__class__.__name__
        for idnum in downloads:
            print '  #[%4d]' % idnum

class Cancel(SimpleDownloadActionClient):
    PARSER_OPTIONS = list(SimpleDownloadActionClient.PARSER_OPTIONS)
    PARSER_OPTIONS.append(Option('--complete', action = 'store_true'))
    PARSER_OPTIONS.append(Option('--delete'  , action = 'store_true'))

    def doCommandWithArgs(self, options, args):
        name = self.getName()
        self.controller.cancel(name, options.complete, options.delete)

class Pause(SimpleDownloadActionClient):
    ACTION = 'pause'
class Resume(SimpleDownloadActionClient):
    ACTION = 'resume'

class Status(SimpleDownloadActionClient):
    ACTION = 'status'
class Complete(SimpleDownloadActionClient):
    PARSER_OPTIONS = list(SimpleDownloadActionClient.PARSER_OPTIONS)
    PARSER_OPTIONS.append(Option('--delete'  , action = 'store_true'))

    def doCommandWithArgs(self, options, args):
        name = self.getName()
        self.controller.complete(name, options.delete)

class Restart(SimpleDownloadActionClient):
    ACTION = 'restart'

class ServerStatus(SimpleServerActionClient):
    ACTION = 'server_status'
class ServerStart(SimpleServerActionClient):
    ACTION = 'server_start'
class ServerShutdown(SimpleServerActionClient):
    ACTION = 'server_shutdown'
class ServerStop(SimpleServerActionClient):
    ACTION = 'server_stop'

class ServerConsole(CommandClient):
    def doCommandWithArgs(self, options, args):
        import readline
        from sys import stdout
        output = stdout.write

        proxy = self.controller.getProxy()
        evaluate = proxy.debug.evaluate
        while True:
            try: syntax = raw_input(' +> ')
            except EOFError:
                print
                break

            try: output(evaluate(syntax))
            except Fault, e:
                output(e.faultString)

# Command Registration.
CLIENT_COMMANDS = dict(initiate = Initiate, cancel   = Cancel  ,
                       pause    = Pause   , resume   = Resume  ,
                       restart  = Restart , complete = Complete,
                       status   = Status  , query    = Query   )

CLIENT_COMMANDS['server-status'  ] = ServerStatus
CLIENT_COMMANDS['server-start'   ] = ServerStart
CLIENT_COMMANDS['server-shutdown'] = ServerShutdown
CLIENT_COMMANDS['server-stop'    ] = ServerStop

CLIENT_COMMANDS['debug-console'  ] = ServerConsole

def getCommandUsage():
    def _getCommandUsage(command, clientClass):
        command = clientClass.__name__
        usage = clientClass.getUsage() or ''
        usage = usage.split('\n')
        usage = filter(None, usage)
        if usage:
            TAB = '\n    '
            usage = '%s%s\n' % (TAB, TAB.join(usage))
            return '  %s:%s' % (command, usage)

        return '  %s' % command

    commands = '\n'.join(_getCommandUsage(name, cls) for (name, cls) \
                           in CLIENT_COMMANDS.iteritems())

    return ('Client Commands:\n' + commands)

def doClientCommand(*argv):
    # Front End.
    # XXX: further usage merging with server.
    if len(argv) > 1 and not argv[1].startswith('--'):
        argv = list(argv)
        clientFactory = CLIENT_COMMANDS.get(argv[1])
        if clientFactory:
            config = ServerConfiguration()
            ctlr = DownloadControllerClient(config.address, config.port)

            client = clientFactory(config, ctlr, prog = argv[0])
            try: client.doCommand(argv[2:])
            except CommandClient.UsageError, e:
                print e.getUsage()
            except Fault, e:
                print e.faultString

        else: # help
            print getCommandUsage()

        return True
